"""
标准化：将数据变换为均值为 0，标准差为 1 的分布
归一化：将数据缩放到[0,1]区间

特征缩放案例：标准化与归一化
演示不同的特征缩放方法及其应用场景
"""

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler, MaxAbsScaler
from sklearn.datasets import load_iris
import seaborn as sns

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']  # Mac系统
plt.rcParams['axes.unicode_minus'] = False

print("=" * 80)
print("特征缩放案例：标准化与归一化")
print("=" * 80)

# 1. 加载数据
iris = load_iris()
X = iris.data
feature_names = iris.feature_names

# 创建DataFrame便于查看
df = pd.DataFrame(X, columns=feature_names)
print("\n1. 原始数据统计信息：")
print(df.describe())

# 2. 标准化 (Standardization/Z-score Normalization)
print("\n" + "=" * 80)
print("2. 标准化 (StandardScaler)")
print("=" * 80)
print("原理：将数据转换为均值为0，标准差为1的分布")
print("公式：z = (x - μ) / σ")
print("适用场景：数据符合正态分布，或算法对数据尺度敏感（SVM、KNN、神经网络）")

scaler_standard = StandardScaler()
X_standard = scaler_standard.fit_transform(X)
df_standard = pd.DataFrame(X_standard, columns=feature_names)

print("\n标准化后的数据统计：")
print(df_standard.describe())
print(f"\n均值：{df_standard.mean().values}")
print(f"标准差：{df_standard.std().values}")

# 3. 归一化 (Min-Max Normalization)
print("\n" + "=" * 80)
print("3. 归一化 (MinMaxScaler)")
print("=" * 80)
print("原理：将数据缩放到[0,1]区间")
print("公式：x' = (x - min) / (max - min)")
print("适用场景：数据有明确边界，需要保留原始分布形状")

scaler_minmax = MinMaxScaler()
X_minmax = scaler_minmax.fit_transform(X)
df_minmax = pd.DataFrame(X_minmax, columns=feature_names)

print("\n归一化后的数据统计：")
print(df_minmax.describe())
print(f"\n最小值：{df_minmax.min().values}")
print(f"最大值：{df_minmax.max().values}")

# 4. 鲁棒缩放 (RobustScaler)
print("\n" + "=" * 80)
print("4. 鲁棒缩放 (RobustScaler)")
print("=" * 80)
print("原理：使用中位数和四分位数进行缩放，对异常值鲁棒")
print("公式：x' = (x - median) / IQR")
print("适用场景：数据包含异常值")

scaler_robust = RobustScaler()
X_robust = scaler_robust.fit_transform(X)
df_robust = pd.DataFrame(X_robust, columns=feature_names)

print("\n鲁棒缩放后的数据统计：")
print(df_robust.describe())

# 5. MaxAbsScaler
print("\n" + "=" * 80)
print("5. 最大绝对值缩放 (MaxAbsScaler)")
print("=" * 80)
print("原理：将数据缩放到[-1, 1]区间，保留稀疏性")
print("公式：x' = x / |max|")
print("适用场景：稀疏数据")

scaler_maxabs = MaxAbsScaler()
X_maxabs = scaler_maxabs.fit_transform(X)
df_maxabs = pd.DataFrame(X_maxabs, columns=feature_names)

print("\n最大绝对值缩放后的数据统计：")
print(df_maxabs.describe())

# 6. 可视化对比
print("\n" + "=" * 80)
print("6. 可视化对比不同缩放方法")
print("=" * 80)

fig, axes = plt.subplots(2, 3, figsize=(18, 10))
fig.suptitle('不同特征缩放方法对比', fontsize=16, fontweight='bold')

# 选择第一个特征进行可视化
feature_idx = 0
feature_name = feature_names[feature_idx]

# 原始数据
axes[0, 0].hist(X[:, feature_idx], bins=30, edgecolor='black', alpha=0.7)
axes[0, 0].set_title(f'原始数据 - {feature_name}')
axes[0, 0].set_xlabel('值')
axes[0, 0].set_ylabel('频数')
axes[0, 0].grid(True, alpha=0.3)

# 标准化
axes[0, 1].hist(X_standard[:, feature_idx], bins=30, edgecolor='black', alpha=0.7, color='orange')
axes[0, 1].set_title(f'标准化 - {feature_name}')
axes[0, 1].set_xlabel('值')
axes[0, 1].set_ylabel('频数')
axes[0, 1].grid(True, alpha=0.3)

# 归一化
axes[0, 2].hist(X_minmax[:, feature_idx], bins=30, edgecolor='black', alpha=0.7, color='green')
axes[0, 2].set_title(f'归一化 - {feature_name}')
axes[0, 2].set_xlabel('值')
axes[0, 2].set_ylabel('频数')
axes[0, 2].grid(True, alpha=0.3)

# 鲁棒缩放
axes[1, 0].hist(X_robust[:, feature_idx], bins=30, edgecolor='black', alpha=0.7, color='red')
axes[1, 0].set_title(f'鲁棒缩放 - {feature_name}')
axes[1, 0].set_xlabel('值')
axes[1, 0].set_ylabel('频数')
axes[1, 0].grid(True, alpha=0.3)

# MaxAbsScaler
axes[1, 1].hist(X_maxabs[:, feature_idx], bins=30, edgecolor='black', alpha=0.7, color='purple')
axes[1, 1].set_title(f'最大绝对值缩放 - {feature_name}')
axes[1, 1].set_xlabel('值')
axes[1, 1].set_ylabel('频数')
axes[1, 1].grid(True, alpha=0.3)

# 箱线图对比
data_comparison = pd.DataFrame({
    '原始': X[:, feature_idx],
    '标准化': X_standard[:, feature_idx],
    '归一化': X_minmax[:, feature_idx],
    '鲁棒': X_robust[:, feature_idx],
    'MaxAbs': X_maxabs[:, feature_idx]
})
data_comparison.boxplot(ax=axes[1, 2])
axes[1, 2].set_title('不同方法箱线图对比')
axes[1, 2].set_ylabel('值')
axes[1, 2].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('/Users/binming/Desktop/CodeBase/python/机器学习/特征工程/2-特征缩放/特征缩放对比.png', dpi=300, bbox_inches='tight')
print("可视化图表已保存")

# 7. 实际应用示例：对模型性能的影响
print("\n" + "=" * 80)
print("7. 特征缩放对KNN模型的影响")
print("=" * 80)

from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

# 分割数据
X_train, X_test, y_train, y_test = train_test_split(X, iris.target, test_size=0.3, random_state=42)

# 不缩放
knn_no_scale = KNeighborsClassifier(n_neighbors=5)
knn_no_scale.fit(X_train, y_train)
y_pred_no_scale = knn_no_scale.predict(X_test)
acc_no_scale = accuracy_score(y_test, y_pred_no_scale)

# 标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

knn_scaled = KNeighborsClassifier(n_neighbors=5)
knn_scaled.fit(X_train_scaled, y_train)
y_pred_scaled = knn_scaled.predict(X_test_scaled)
acc_scaled = accuracy_score(y_test, y_pred_scaled)

print(f"不缩放的准确率: {acc_no_scale:.4f}")
print(f"标准化后的准确率: {acc_scaled:.4f}")
print(f"提升: {(acc_scaled - acc_no_scale):.4f}")

# 8. 注意事项
print("\n" + "=" * 80)
print("8. 特征缩放注意事项")
print("=" * 80)
print("""
1. **训练集和测试集分离**：
   - 先分割数据，再进行特征缩放
   - 在训练集上fit，在测试集上transform
   - 避免数据泄露

2. **选择合适的缩放方法**：
   - StandardScaler：数据近似正态分布
   - MinMaxScaler：数据有明确边界
   - RobustScaler：数据包含异常值
   - MaxAbsScaler：稀疏数据

3. **不是所有算法都需要缩放**：
   - 需要：KNN、SVM、神经网络、线性回归（带正则化）
   - 不需要：决策树、随机森林、XGBoost等树模型

4. **保存缩放器**：
   - 使用joblib或pickle保存scaler
   - 部署时使用相同的scaler转换新数据
""")

# 9. 保存和加载缩放器
print("\n" + "=" * 80)
print("9. 保存和加载缩放器")
print("=" * 80)

import joblib

# 保存缩放器
joblib.dump(scaler, '/Users/binming/Desktop/CodeBase/python/机器学习/特征工程/2-特征缩放/scaler.pkl')
print("缩放器已保存到 scaler.pkl")

# 加载缩放器
loaded_scaler = joblib.load('/Users/binming/Desktop/CodeBase/python/机器学习/特征工程/2-特征缩放/scaler.pkl')
print("缩放器已加载")

# 使用加载的缩放器
new_data = np.array([[5.1, 3.5, 1.4, 0.2]])
scaled_new_data = loaded_scaler.transform(new_data)
print(f"\n新数据: {new_data}")
print(f"缩放后: {scaled_new_data}")

print("\n" + "=" * 80)
print("案例完成！")
print("=" * 80)
